#
#    Copyright 2006 Andrew Wilkinson <aw@cs.york.ac.uk>.
#
#    This file is part of PyScobble (http://lastfm.indiegigs.co.uk/pyscrobble)
#
#    PyScobble is free software; you can redistribute it and/or modify
#    it under the terms of the GNU Lesser General Public License as published by
#    the Free Software Foundation; either version 2.1 of the License, or
#    (at your option) any later version.
#
#    PyScobble is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public License
#    along with PyScobble; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

import sys
import threading
from PyQt4 import QtCore, QtGui
import traceback

import scrobble

import resource
import mainwindow
import working
import chain

defaults = {"friends": ("andrew_j_w", "Russ"), "neighbours": ("LAST.HQ", "lumberjack"), "similarArtists": ("The Beatles", "Interpol")}

workingwin = None
thread = None
path = None
exception = None
cancel = False

def runGUI(psixdegrees, pchaintypes):
    global sixdegrees, chaintypes
    sixdegrees, chaintypes = psixdegrees, pchaintypes

    app = QtGui.QApplication(sys.argv)
    window = QtGui.QDialog()

    ui = MainWindow()
    ui.setupUi(window)

    window.show()

    sys.exit(app.exec_())

class MainWindow(mainwindow.Ui_MainWindow):
    def __init__(self):
        mainwindow.Ui_MainWindow.__init__(self)

        self.chainType = "friends"
        self.mutual = QtCore.Qt.Checked
        self.mutualPrev = QtCore.Qt.Unchecked

    def setupUi(self, MainWindow):
        mainwindow.Ui_MainWindow.setupUi(self, MainWindow)

        MainWindow.connect(self.friendsButton, QtCore.SIGNAL("toggled(bool)"), self.friendToggled)
        MainWindow.connect(self.neighboursButton, QtCore.SIGNAL("toggled(bool)"), self.neighboursToggled)
        MainWindow.connect(self.similarArtistsButton, QtCore.SIGNAL("toggled(bool)"), self.similarArtistsToggled)
        MainWindow.connect(self.mutualLinks, QtCore.SIGNAL("stateChanged(int)"), self.mutualChanged)
        MainWindow.connect(self.FindChains, QtCore.SIGNAL("clicked()"), self.findChainsClicked)

        self.friendsButton.setChecked(QtCore.Qt.Checked)
        self.mutualLinks.setCheckState(QtCore.Qt.Checked)
        self.mutualLinks.setEnabled(False)

    def friendToggled(self, value):
        if value and self.chainType == "friends":
            pass
        elif value:
            if (self.chainType == "neighbours" and self.chainStart.text() == defaults["neighbours"][0] and self.chainEnd.text() == defaults["neighbours"][1]) or (self.chainType == "similarArtists" and self.chainStart.text() == defaults["similarArtists"][0] and self.chainEnd.text() == defaults["similarArtists"][1]):
                self.chainStart.setText(defaults["friends"][0])
                self.chainEnd.setText(defaults["friends"][1])
            self.chainType = "friends"
            self.mutualPrev = self.mutual
            self.mutualLinks.setCheckState(QtCore.Qt.Checked)
            self.mutualLinks.setEnabled(False)
        else:
            self.mutualLinks.setEnabled(True)
            self.mutualLinks.setCheckState(self.mutualPrev)
            self.mutual = self.mutualPrev
    def neighboursToggled(self, value):
        if value:
            if (self.chainType == "friends" and self.chainStart.text() == defaults["friends"][0] and self.chainEnd.text() == defaults["friends"][1]) or (self.chainType == "similarArtists" and self.chainStart.text() == defaults["similarArtists"][0] and self.chainEnd.text() == defaults["similarArtists"][1]):
                self.chainStart.setText(defaults["neighbours"][0])
                self.chainEnd.setText(defaults["neighbours"][1])
            self.chainType = "neighbours"
    def similarArtistsToggled(self, value):
        if value:
            if (self.chainType == "friends" and self.chainStart.text() == defaults["friends"][0] and self.chainEnd.text() == defaults["friends"][1]) or (self.chainType == "neighbours" and self.chainStart.text() == defaults["neighbours"][0] and self.chainEnd.text() == defaults["neighbours"][1]):
                self.chainStart.setText(defaults["similarArtists"][0])
                self.chainEnd.setText(defaults["similarArtists"][1])
            self.chainType = "similarArtists"

    def mutualChanged(self, state):
        if state == 2:
            self.mutual = QtCore.Qt.Checked
        else:
            self.mutual = QtCore.Qt.Unchecked

    def findChainsClicked(self):
        global workingwin, thread, path, exception

        path = None
        exception = None
        cancel = False
        scrobble.resetStats()

        if len(self.chainStart.text()) == 0:
            QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Please enter a value for the start of the chain")
            return
        if len(self.chainEnd.text()) == 0:
            QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Please enter a value for the end of the chain")
            return

        workingwin = QtGui.QDialog()
        ui = WorkingWindow()
        ui.setupUi(workingwin)
        ui.isMutual = self.mutual

        if self.chainType == "friends":
            try:
                f = scrobble.User(str(self.chainStart.text())).friends
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s does not exist." % (self.chainStart.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s has no friends :-(" % (self.chainStart.text(),))
                    return
            try:
                f = scrobble.User(str(self.chainEnd.text())).friends
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s does not exist." % (self.chainEnd.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s has no friends :-(" % (self.chainEnd.text(),))
                    return
            thread = FindChains((scrobble.User(str(self.chainStart.text())),
                                 scrobble.User(str(self.chainEnd.text())), chaintypes.getUserFriends, chaintypes.getUserReverseFriends, 
                                 True, None, ui.notify))
        elif self.chainType == "neighbours":
            try:
                f = scrobble.User(str(self.chainStart.text())).neighbours
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s does not exist." % (self.chainStart.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s has no neighbours :-(" % (self.chainStart.text(),))
                    return
            try:
                f = scrobble.User(str(self.chainEnd.text())).neighbours
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s does not exist." % (self.chainEnd.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "User %s has no neighbours :-(" % (self.chainEnd.text(),))
                    return
            thread = FindChains((scrobble.User(str(self.chainStart.text())),
                                 scrobble.User(str(self.chainEnd.text())), chaintypes.getUserNeighbours, chaintypes.getUserReverseNeighbours, False, chaintypes.isNeighbourMutual if self.mutual else None, ui.notify))
        elif self.chainType == "similarArtists":
            try:
                f = scrobble.Artist(str(self.chainStart.text())).similar
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Artist %s does not exist." % (self.chainStart.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Artist %s has no similar artists :-(" % (self.chainStart.text(),))
                    return
            try:
                f = scrobble.Artist(str(self.chainEnd.text())).similar
            except ValueError:
                QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Artist %s does not exist." % (self.chainEnd.text(),))
                return
            else:
                if len(f) == 0:
                    QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "Artist %s has no similar artists :-(" % (self.chainEnd.text(),))
                    return
            thread = FindChains((scrobble.Artist(str(self.chainStart.text())),
                                 scrobble.Artist(str(self.chainEnd.text())), chaintypes.getArtistSimilar, 
                                 chaintypes.getArtistReverseSimilar, False, chaintypes.isArtistSimilarMutual if self.mutual else None, 
                                 ui.notify))

        thread.start()

        workingwin.exec_()

class WorkingWindow(working.Ui_Working):
    def setupUi(self, MainWindow):
        working.Ui_Working.setupUi(self, MainWindow)

        self.depthBar.setMinimum(0)
        self.depthBar.setMaximum(10)
        self.depthBar.setValue(0)
        self.depthLabel.setText("0")

        self.checkedBar.setMinimum(0)
        self.checkedBar.setMaximum(10000)
        self.checkedBar.setValue(0)
        self.checkedLabel.setText("0")

        self.remainingBar.setMinimum(1)
        self.remainingBar.setMaximum(10000)
        self.remainingBar.setValue(1)
        self.remainingLabel.setText("1")

        self.cacheUsage.setMinimum(0)
        self.cacheUsage.setMaximum(1000)

        self.notifylist = []

        self.timer = QtCore.QTimer()
        MainWindow.connect(self.timer, QtCore.SIGNAL("timeout()"), self.notifyTimer);
        self.timer.start(1000);

        MainWindow.connect(self.cancelButton, QtCore.SIGNAL("clicked()"), self.cancel);
        MainWindow.connect(MainWindow, QtCore.SIGNAL("finished(int)"), self.finished);

    def finished(self, r):
        global path

        self.timer.stop()
        if thread is not None:
            thread.end = True
        if exception is not None:
             QtGui.QMessageBox.critical(None, "Last.fm 6 Degrees Of Separation", "An error occurred. Please email the traceback below, plus details of the chain you were trying to find to andrew@indiegis.co.uk<br><br>%s" % ("<br>".join(exception),))
        elif path is not None:
            if self.isMutual:
                sep = " &lt;-&gt; "
            else:
                sep = " -&gt; "
            path = sep.join([x.getLink() for x in path])

            win = QtGui.QDialog()
            ui = ChainWindow()
            ui.setupUi(win)
            win.exec_()
        elif not cancel:
             QtGui.QMessageBox.information(None, "Last.fm 6 Degrees Of Separation", "Sorry, no chain were found.")

    def cancel(self):
        global cancel
        cancel = True
        workingwin.reject()

    def notify(self, text, value):
        self.notifylist.append((text, value))

    def notifyTimer(self):
        while len(self.notifylist) > 0:
            (text, value), self.notifylist = self.notifylist[0], self.notifylist[1:]
            if text == "depth":
                if value > 10:
                    self.depthBar.setMaximum(((value/10)*10)+10)
                self.depthBar.setValue(value)
                self.depthLabel.setText(str(value))
            elif text == "checked":
                if value > 10000:
                    self.checkedBar.setMaximum(((value/10000)*10000)+10000)
                self.checkedBar.setValue(value)
                self.checkedLabel.setText(str(value))
            elif text == "remaining":
                if value > 10000:
                    self.remainingBar.setMaximum(((value/10000)*10000)+10000)
                self.remainingBar.setValue(value)
                self.remainingLabel.setText(str(value))
        self.cacheUsage.setValue(int(scrobble.getCacheUsage()*10))
        self.currentStatus.setText(scrobble.getStatus())
        if thread and thread.end:
            workingwin.accept()

class FindChains(QtCore.QThread):
    def __init__(self, args):
        QtCore.QThread.__init__(self)
        self.args = args
        self.end = False

    def run(self):
        global path, exception
        gen = sixdegrees(*self.args)
        path = None
        try:
            while not self.end and path is None:
                path = gen.next()
        except:
            exctype, excvalue, tb = sys.exc_info()
            exception = traceback.format_exception(exctype, excvalue, tb)
            exception = [e.replace("<", "&lt;").replace(">", "&gt;") for e in exception]
        if path is False:
            path = None
        self.end = True

class ChainWindow(chain.Ui_ChainWindow):
    def setupUi(self, MainWindow):
        chain.Ui_ChainWindow.setupUi(self, MainWindow)

        self.chaintext.setHtml(path)
